﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class MD5Utility
{
    static MD5 md5;
    static MD5Utility()
    {
        md5 = MD5.Create();
    }

    public static string GetMD5Str(byte[] md5Data)
    {
        string destString = "";
        for (int i = 0; i < md5Data.Length; i++)
        {
            destString += System.Convert.ToString(md5Data[i], 16).PadLeft(2, '0');
        }
        destString = destString.PadLeft(32, '0');
        return destString;
    }

    public static byte[] GetMD5(string str)
    {
        lock (md5)
        {
            byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
            return hash;
        }
    }

    public static byte[] GetMD5(byte[] bytes)
    {
        lock (md5)
        {
            byte[] hash = md5.ComputeHash(bytes);
            return hash;
        }
    }

    public static byte[] GetMD5(Stream filestream)
    {
        lock (md5)
        {
            byte[] hash = md5.ComputeHash(filestream);
            return hash;
        }
    }

    public static byte[] FileToMD5(string path)
    {
        if (File.Exists(path))
        {
            using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 8192))
            {
                return GetMD5(fs);
            }
        }
        else
            return new byte[16];
    }



    public static void FileToMD5Async(string path, Action<byte[]> complete, Action<long, long> process)
    {
        MD5Utility mD5 = new MD5Utility();

        mD5.AsyncCheck(path, process).OnCompleted(() => {
            complete?.Invoke(mD5.Md5Result);
        });

    }



    //支持所有哈希算法
    private HashAlgorithm hashAlgorithm;

    //文件缓冲区
    private static byte[] buffer;

    //文件读取流
    private static Stream inputStream;
    Action<long, long> OnProcess;
    public byte[] Md5Result;
    //bool complete = false;

    public event Action onComplete;
    //大文件异步md5
    public TaskAwaiter AsyncCheck(string path, Action<long, long> process)
    {
        OnProcess = process;
        int bufferSize = 1048576;//缓冲区大小，1MB

        buffer = new byte[bufferSize];

        //打开文件流
        inputStream = File.Open(path, FileMode.Open);
        hashAlgorithm = new MD5CryptoServiceProvider();
        //异步读取数据到缓冲区
        inputStream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(AsyncComputeHashCallback), null);


        return GetAwaiter();
    }

    private void AsyncComputeHashCallback(IAsyncResult result)
    {
        int bytesRead = inputStream.EndRead(result);

        //检查是否到达流末尾
        if (inputStream.Position < inputStream.Length)
        {
            //输出进度
            OnProcess?.Invoke(inputStream.Position, inputStream.Length);

            var output = new byte[buffer.Length];
            //分块计算哈希值
            hashAlgorithm.TransformBlock(buffer, 0, buffer.Length, output, 0);

            //异步读取下一分块
            inputStream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(AsyncComputeHashCallback), null);
            return;
        }
        else
        {
            //计算最后分块哈希值
            hashAlgorithm.TransformFinalBlock(buffer, 0, bytesRead);
        }

        Md5Result = hashAlgorithm.Hash;
        //关闭流
        inputStream.Close();
        onComplete?.Invoke();
        OnProcess = null;
    }

    public TaskAwaiter GetAwaiter()
    {
        var tcs = new TaskCompletionSource<object>();
        onComplete += () =>
        {
            tcs.SetResult(null);
        };
        return ((Task)tcs.Task).GetAwaiter();
    }
}